﻿/*
	This file is part of Limpet.

    Limpet is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Limpet is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Limpet.  If not, see http://www.gnu.org/licenses/.
	
	Copyright 2010 Dan Popick
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Open.Web.Diagnostics.Limpet
{
    /// <summary>
    /// An AES-encryption stream, for use the page filters
    /// </summary>
    /// <remarks>
    /// The output stream is prefixed by the IV, in the form
    /// [one byte IV length][IV][encrypted data]. Also, this 
    /// class has several quirks which make it compatible with 
    /// page filers, such as the assumption that Flush() is only
    /// called on completion and that it is not safe to write to
    /// the underlying stream in the constructor.
    /// </remarks>
    public class PageFilterEncryptionStream : Stream
    {
        // the AES key
        static byte[] _key;

        static PageFilterEncryptionStream()
        {
            _key = Convert.FromBase64String(LimpetManager.Config.Base64AesKey);
        }

        // underlying stream to which the encrypted data is written
        Stream _internalStream;
        // the crypto stream
        CryptoStream _baseStream;
        // the Rijndael object used with the cryptostream
        RijndaelManaged _rijn = new RijndaelManaged();

        // indicates whether the IV header has been written yet
        bool isHeaderWritten = false;

        public PageFilterEncryptionStream(Stream baseStream)
        {
            _rijn = new RijndaelManaged();
            _rijn.Mode = CipherMode.CBC;
            _rijn.Key = _key;
            _rijn.GenerateIV();

            _internalStream = baseStream;
            _baseStream = new CryptoStream(
                _internalStream,
                _rijn.CreateEncryptor(),
                CryptoStreamMode.Write);
        }

        public override bool CanRead { get { return true; } }
        public override bool CanSeek { get { return false; } }
        public override bool CanWrite { get { return true; } }
        public override void Flush()
        {
            _baseStream.FlushFinalBlock();
        }
        public override long Length
        {
            get { throw new InvalidOperationException(); }
        }

        public override long Position
        {
            get { throw new InvalidOperationException(); }
            set { throw new InvalidOperationException(); }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return _baseStream.Read(buffer, offset, count);
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new InvalidOperationException();
        }

        public override void SetLength(long value)
        {
            throw new InvalidOperationException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            // write the header on the first write request
            if (!isHeaderWritten)
            {
                _internalStream.WriteByte((byte)_rijn.IV.Length);
                _internalStream.Write(_rijn.IV, 0, _rijn.IV.Length);
                isHeaderWritten = true;
            }

            _baseStream.Write(buffer, offset, count);
        }
    }
}
